package aquactl.comapi;

import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import aquactl.comapi.objects.Timer;

public class ComApi {
	private static final long TIMEOUT = 1000 * 50;
	private boolean initialized = false;
	private InputStreamReader in = null;
	private OutputStreamWriter out = null;

	@SuppressWarnings("unchecked")
	public static List<String> getComPorts() {
		Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
		ArrayList<String> out = new ArrayList<String>();
		while (portIdentifiers.hasMoreElements()) {
			CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers
					.nextElement();
			if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL) {
				out.add(pid.getName());
			}
		}
		return out;
	}

	@SuppressWarnings("unchecked")
	public void init(String portName) throws ComException {
		Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
		CommPortIdentifier portId = null;
		while (portIdentifiers.hasMoreElements()) {
			CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers
					.nextElement();
			if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL
					&& pid.getName().equals(portName)) {
				portId = pid;
				break;
			}
		}
		if (portId == null) {
			throw new ComException("Could not find serial port " + portName);
		}
		SerialPort port = null;
		try {
			port = (SerialPort) portId.open("AQUACtl", // Name of the
					// application
					// asking for the port
					10000 // Wait max. 10 sec. to acquire port
					);
		} catch (PortInUseException e) {
			throw new ComException("Could not init serial port " + portName);
		}
		//
		// Set all the params.
		// This may need to go in a try/catch block which throws
		// UnsupportedCommOperationException
		//
		try {
			port.setSerialPortParams(115200, SerialPort.DATABITS_8,
					SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
		} catch (UnsupportedCommOperationException e) {
			throw new ComException("Could not init serial port " + portName);
		}
		try {
			in = new InputStreamReader(port.getInputStream());
			out = new OutputStreamWriter(port.getOutputStream());
		} catch (IOException e) {
			throw new ComException("Could not init serial port " + portName);
		}
		setInitialized(true);
	}

	public void setInitialized(boolean initialized) {
		this.initialized = initialized;
	}

	public boolean isInitialized() {
		return initialized;
	}

	private Timer parseTimer(String input) throws ComException {
		String[] strings = input.split(":");
		if (strings.length != 8) {
			throw new ComException("Malformed response: " + input);
		}
		Timer timer = new Timer();
		timer.setId(Integer.parseInt(strings[0]));
		timer.setHourOn(Integer.toHexString(Integer.parseInt(strings[1])));
		timer.setMinuteOn(Integer.toHexString(Integer.parseInt(strings[2])));
		timer.setHourOff(Integer.toHexString(Integer.parseInt(strings[3])));
		timer.setMinuteOff(Integer.toHexString(Integer.parseInt(strings[4])));
		timer.setOn(Integer.parseInt(strings[5]) == 0 ? false : true);
		timer.setPowerId(Integer.parseInt(strings[6]));
		timer.setActive(Integer.parseInt(strings[7]) == 0 ? false : true);
		return timer;
	}

	public Set<Timer> readTimers() throws ComException {
		if (!isInitialized()) {
			throw new ComException("API not initialized.");
		}
		Set<Timer> timers = new TreeSet<Timer>();
		long startTime = 0L;
		try {
			writeChar('s');
			startTime = System.currentTimeMillis();
			while (!in.ready()) {
				if (System.currentTimeMillis() - startTime > TIMEOUT) {
					throw new ComException("Connection timeout");
				}
			}
			int c = in.read();
			StringBuilder buff = new StringBuilder();
			while (c != '|') {
				while (c != '.') {
					buff.append(String.valueOf(c) + ":");
					c = in.read();
				}
				// write timer
				System.out.println(buff.toString());
				timers.add(parseTimer(buff.toString()));
				buff.delete(0, buff.capacity());
				c = in.read();
			}
		} catch (IOException e) {
			throw new ComException(e);
		}

		return timers;
	}

	public void writeTimers(Set<Timer> timers) throws ComException {
		for (Timer timer : timers) {
			writeTimer(timer);
		}
	}

	public void writeTimer(Timer timer) throws ComException {
		try {
			writeChar('p');
			writeChar(timer.getId());
			writeChar(Integer.parseInt(timer.getHourOn(), 16));
			writeChar(Integer.parseInt(timer.getMinuteOn(), 16));
			writeChar(Integer.parseInt(timer.getHourOff(), 16));
			writeChar(Integer.parseInt(timer.getMinuteOff(), 16));
			writeChar(timer.isOn() == true ? 1 : 0);
			writeChar(timer.isActive() == true ? 1 : 0);
			Thread.sleep(1000);
			System.out.println(timer.getId() + ":"
					+ Integer.parseInt(timer.getHourOn(), 16) + ":"
					+ Integer.parseInt(timer.getMinuteOn(), 16) + ":"
					+ Integer.parseInt(timer.getHourOff(), 16) + ":"
					+ Integer.parseInt(timer.getMinuteOff(), 16) + ":"
					+ (timer.isOn() == true ? 1 : 0) + ":"
					+ (timer.isActive() == true ? 1 : 0));
		} catch (IOException e) {
			throw new ComException(e);
		} catch (InterruptedException e) {
			throw new ComException(e);
		}
	}

	public void setClock(String time) throws ComException {
		try {
			writeChar('t');
			writeChar(Integer.parseInt(time.substring(0, 2), 16));
			writeChar(Integer.parseInt(time.substring(2, 4), 16));
		} catch (IOException e) {
			throw new ComException(e);
		}
	}

	public String getClock() throws ComException {
		StringBuilder buff = new StringBuilder();
		try {
			writeChar('c');
			NumberFormat format = NumberFormat.getNumberInstance();
			DecimalFormat df = (DecimalFormat) format;
			df.applyPattern("00");
			buff.append(df.format(Integer.parseInt(Integer
					.toHexString(readBlocking()))));
			buff.append(":");
			buff.append(df.format(Integer.parseInt(Integer
					.toHexString(readBlocking()))));
			readBlocking();
		} catch (IOException e) {
			throw new ComException(e);
		}
		return buff.toString();
	}

	private int readBlocking() throws IOException {
		while (!in.ready()) {
		}
		return in.read();
	}

	private void writeChar(int c) throws IOException {
		try {
			out.write(c);
			out.flush();
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// ignore
		}
	}

	public String readTemp() throws ComException {
		int c = 0;
		StringBuilder sb = new StringBuilder();
		try {
			writeChar('m');
			sb.append((char) readBlocking());
			sb.append((char) readBlocking());
			sb.append(".");
			sb.append((char) readBlocking());
			sb.append((char) readBlocking());
			sb.append(" C");
		} catch (IOException e) {
			throw new ComException(e);
		}
		return sb.toString();
	}

	public void switchPower(int id) throws ComException {
		try {
			writeChar('o');
			writeChar(id);
		} catch (IOException e) {
			throw new ComException(e);
		}
	}
}
